#pragma once

#include "log.hpp"
#include "util.hpp"
#include "dataManage.hpp"
#include "onlineManage.hpp"
#include "roomManage.hpp"
#include "sessionManage.hpp"
#include "matchManage.hpp"
#include <functional>
#include <mutex>
#include <string>
#include <vector>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

/*-------------------------------------------gobang_server模块--------------------------------------------
作用：利用Websocketpp库搭建一个游戏服务器，处理网络请求等等动作*/
class gobang_server
{
public:
    // 单例模式
    static gobang_server &get_instance()
    {
        if (_pgs == nullptr)// 这里是效率问题，不为空的情况下就不需要再加锁判断了
        {
            // 为了避免线程之间的竞争，这里加锁
            std::unique_lock<std::mutex> lock(_mutex);
            if (_pgs == nullptr)
            {
                _pgs = new gobang_server();// 如果暂时还没有实例的情况下new出来一个
            }
        }
        return *_pgs;
    }

    void open_callback(websocketpp::connection_hdl hdl)// websocket长连接创建成功的回调函数
    {
        //1. 获取长连接指针
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        if(conn.get() == nullptr)
        {
            ERROR_LOG("获取websocket长连接失败！原因：指针为空!");
            return;
        }
        //2. 获得请求
        websocketpp::http::parser::request resp = conn->get_request();// 从连接当中获取请求
        std::string uri = resp.get_uri();// 获得uri
        if(uri == "/hall")
        {
            websocket_hall(conn);// 如果获取的是游戏大厅资源
            return;
        }
        else if(uri == "/room")
        {
            websocket_room(conn);// 如果获取的是游戏房间资源
            return;
        }
    }

    void close_callback(websocketpp::connection_hdl hdl)// websockt长连接断开的回调函数
    {
        //1. 获取连接
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        if(conn.get() == nullptr)
        {
            ERROR_LOG("长连接断开回调函数执行失败！原因：连接指针为空！");
            return;
        }
        //2. 根据请求判断是哪里断开
        websocketpp::http::parser::request resp = conn->get_request();
        std::string uri = resp.get_uri();
        if(uri == "/hall")
        {
            websocket_hall_broken(conn);// 如果是要断开游戏大厅的长连接
            return;
        }
        else if(uri == "/room")
        {
            websocket_room_broken(conn);// 如果是要断开游戏房间的长连接
            return;
        }
    }

    void http_callback(websocketpp::connection_hdl hdl)// http的回调函数
    {
        // 1. 获得http请求的连接
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        if (conn.get() == nullptr)
        {
            ERROR_LOG("获取http请求连接失败！原因：返回的连接指针为空！");
            return;
        }
        // 2. 获得http请求
        websocketpp::http::parser::request resp = conn->get_request();
        std::string uri = resp.get_uri();
        std::string method = resp.get_method();// websocketpp库会自动解析HTTP请求
        // 3. 根据http的不同请求做出不同的动作
        if(method == "POST" && uri == "/register")
        {
            register_request(conn);// 注册动作
            return;
        }
        else if(method == "POST" && uri == "/login")
        {
            login_request(conn);// 登录动作
            return;
        }
        else if(method == "GET" && uri == "/info")
        {
            info_request(conn);// 获取信息动作
            return;
        }
        else 
        {   
            static_resource(conn);// 静态资源的获取
            return;
        }
    }

    void message_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)// 长连接的消息处理函数
    {
        /*长连接上有消息到来，进行一个处理*/
        //1. 获得连接
        wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
        if(conn.get() == nullptr)
        {
            ERROR_LOG("消息处理失败！原因：连接指针为空！");
            return;
        }
        //2. 获得请求
        websocketpp::http::parser::request resp = conn->get_request();
        std::string uri = resp.get_uri();
        if(uri == "/hall")
        {
            hall_message(conn,msg);// 处理游戏大厅
            return;
        }
        else if(uri == "/room")
        {
            room_message(conn,msg);// 处理游戏房间
            return;
        }
    }

    // 服务器启动
    void start(int port)
    {
        _server.listen(port);
        _server.start_accept();
        _server.run();
    }

    void static_resource(wsserver_t::connection_ptr conn)// 静态资源获取
    {
        //1. 获得请求uri
        websocketpp::http::parser::request resp = conn->get_request();
        std::string uri = resp.get_uri();
        uri = wwwroot + uri;
        //2. 判断uri访问的是路径还是具体资源
        if(uri.back() == '/') uri = wwwroot + "/index.html";// 添加上默认路径
        DEBUG_LOG("得到http-uri路径：%s",uri.c_str());
        //3. 判断这个资源是否存在
        std::string out;
        bool ret = file_util::read_file(uri,out);
        if(ret == false)// 如果读取文件失败
        {
            out += "<html>";
            out += "<head>";
            out += "<meta charset='utf-8'>";
            out += "</head>";
            out += "<body>";
            out += "<h1> Not Found </h1>";
            out += "</body>";
            out += "</html>";
            conn->set_body(out);
            conn->set_status(websocketpp::http::status_code::not_found);
        }
        // 否则设置正确的响应状态码
        conn->set_body(out);
        conn->set_status(websocketpp::http::status_code::ok);
    }

    void register_request(wsserver_t::connection_ptr conn)// 注册请求
    {
        Json::Value resp;
        //1. 获得注册请求正文
        std::string req_body = conn->get_request_body();
        //2. 反序列化得到json对象
        Json::Value req;
        bool ret = json_util::deserialize(req_body,req);
        if(ret == false)
        {
            ERROR_LOG("注册失败！原因：反序列化失败！");
            return;
        }
        //3. 判断用户名或密码是否为空
        if(req["username"].asString().empty() || req["password"].asString().empty())
        {
            resp["optype"] = "register";
            resp["result"] = false;
            resp["reason"] = "用户名或密码为空！注册失败！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        //4. 成功注册
        ret = _dm.user_register(req);
        if(ret == false)
        {
            resp["optype"] = "register";
            resp["result"] = false;
            resp["reason"] = "用户注册失败！原因：该用户名已存在！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        resp["optype"] = "register";
        resp["result"] = true;
        resp["reason"] = "用户注册成功！";
        std::string body;
        json_util::serialize(resp,body);
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");
    }

    void login_request(wsserver_t::connection_ptr conn)// 登录请求
    {
        Json::Value resp;
        //1. 获得http请求正文
        std::string req_body = conn->get_request_body();
        //2. 反序列化得到json对象
        Json::Value req;
        bool ret = json_util::deserialize(req_body,req);
        if(ret == false)
        {
            ERROR_LOG("注册失败！原因：反序列化失败！");
            return;
        }
        //3. 判断用户名或密码是否为空
        if(req["username"].asString().empty() || req["password"].asString().empty())
        {
            resp["optype"] = "login";
            resp["result"] = false;
            resp["reason"] = "用户名或密码为空！登录失败！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        //4. 判断能否登录
        ret = _dm.user_login(req);// 与数据库保存的数据做对比，并将数据库的内容拿出来
        if(ret == false)
        {
            resp["optype"] = "login";
            resp["result"] = false;
            resp["reason"] = "登录失败！原因：用户名密码错误!";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::internal_server_error);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        uint64_t uid = req["uid"].asInt64();// 获取uid
        //  4.1判断是否已经在线
        if(_om.is_in_hall(uid) || _om.is_in_room(uid))
        {
            resp["optype"] = "login";
            resp["result"] = false;
            resp["reason"] = "登录失败！原因：该用户已经登录！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        } 
        //5. 将该用户放入在线用户模块当中
        //_om.enter_hall(uid,conn);
        //6. 为该用户创建一个session
        DEBUG_LOG("准备为登录成功的用户id%d创建session！",uid);
        session_t ssp = _sm.create_session(uid);// 为登录成功的用户创建session
        if(ssp.get() == nullptr)// 如果没有创建session成功
        {
            resp["optype"] = "login";
            resp["result"] = false;
            resp["reason"] = "创建session失败！原因：服务器错误！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::internal_server_error);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        //8. 返回响应
        resp["optype"] = "login";
        resp["result"] = true;
        std::string body;
        json_util::serialize(resp,body);
        std::string cookie = "SSID=" + std::to_string(ssp->get_ssid());
        conn->append_header("Set-Cookie", cookie);// 响应报文中有Set-Cookie字段
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");
        //9. 设置一个定时器
        _sm.set_session_expire_time(ssp->get_ssid(),SESSION_TIMEOUT);// 登录成功之后添加定时器
    }

    // 辅助函数
    void get_cookie_val(const std::string &des,const std::string &sep,std::string &out)
    {
        std::vector<std::string> vec;
        string_util::split(des,"; ",vec);
        for(auto &str:vec)
        {
            std::vector<std::string> tmp;
            string_util::split(str,"=",tmp);
            if(tmp.size() < 2) continue;
            if(tmp[0] == sep)
            {
                out = tmp[1];
                return;
            }
        }
    }

    // 辅助函数
    size_t get_ssid_from_cookie(wsserver_t::connection_ptr conn)
    {
        Json::Value resp;
        //1. 读取cookie信息
        //  1.获取Cookie头部
        std::string cookie_str = conn->get_request_header("Cookie");
        if(cookie_str.empty())
        {
            ERROR_LOG("获取用户信息失败！原因：没有找到对应的Cookie选项!");
            resp["optype"] = "info";
            resp["result"] = false;
            resp["reason"] = "获取用户信息失败！原因：没有找到对应的Cookie选项!";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return -1;
        }
        //  2.从Cookie头部获取ssid
        std::string ssid_str;
        get_cookie_val(cookie_str,"SSID",ssid_str);
        if(ssid_str.empty())
        {
            ERROR_LOG("获取用户信息失败！原因：解析不出来ssid！");
            resp["optype"] = "info";
            resp["result"] = false;
            resp["reason"] = "取用户信息失败！原因：解析不出来ssid！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::internal_server_error);
            conn->append_header("Content-Type", "application/json");
            return -1;
        }
        return std::stol(ssid_str);
    }
    
    void info_request(wsserver_t::connection_ptr conn)// 登录成功之后获取用户信息
    {
        Json::Value resp;
        size_t ssid = get_ssid_from_cookie(conn);// 从客户端发起的请求当中提取cookie和ssid
        
        //2. 利用ssid找到用户的uid
        session_t ssp = _sm.find_by_ssid(ssid);// 利用ssid找到对应的session
        if(ssp.get() == nullptr)
        {
            ERROR_LOG("获取用户信息失败！原因：ssid%d对应的session对象不存在！",ssid);
            resp["optype"] = "info";
            resp["result"] = false;
            resp["reason"] = "获取用户信息失败！原因：ssid对应的session对象不存在！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        uint64_t uid = ssp->get_user_id();// session对象当中保存的有uid
        //3. 使用用户的uid找到用户的信息
        Json::Value user_info;
        bool ret = _dm.user_info_by_id(uid,user_info);
        if(ret == false)
        {
            ERROR_LOG("获取用户信息失败！原因：获取用户id%d的信息失败！",uid);
            resp["optype"] = "info";
            resp["result"] = false;
            resp["reason"] = "获取用户信息失败！原因：获取用户的信息失败！";
            std::string body;
            json_util::serialize(resp,body);
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::bad_request);
            conn->append_header("Content-Type", "application/json");
            return;
        }
        //4. 序列化
        std::string body;
        json_util::serialize(user_info,body);
        //5. 发送响应
        conn->set_status(websocketpp::http::status_code::ok);
        //conn->append_header("Content-Length",std::to_string(body.size()+1));
        conn->append_header("Content-Type", "application/json");
        conn->set_body(body);
        //6. 刷新定时器
        _sm.set_session_expire_time(ssid,SESSION_TIMEOUT);
    }

    void websocket_hall(wsserver_t::connection_ptr conn)// 客户端申请协议升级，升级成为Websocket长连接之后的游戏大厅的回调函数
    {
        Json::Value resp;
        //1. 获取Cookie信息
        //  1.获取Cookie头部信息
        //  2.获取ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 利用ssid找到用户uid
        session_t ssp = _sm.find_by_ssid(ssid);
        //3. 判断session对象是否存在
        if(ssp.get() == nullptr)
        {
            resp["result"] = false;
            resp["reason"] = "游戏大厅长连接建立失败！原因：session对象不存在！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        /*以上都是在做ssid是否合法的认证*/
        //4. 将用户放入游戏大厅
        uint64_t uid = ssp->get_user_id();// 获取uid
        _om.enter_hall(uid,conn);
        //5. 返回长连接建立成功的响应
        resp["optype"] = "hall_ready";
        resp["result"] = true;
        resp["reason"] = "建立长连接成功！";
        std::string body;
        json_util::serialize(resp,body);
        conn->send(body);
        //6. 定时器设置为永久
        _sm.set_session_expire_time(ssid,SESSION_FOREVER);// TODO
    }

    //11. 游戏房间的回调函数
    void websocket_room(wsserver_t::connection_ptr conn)
    {
        Json::Value resp;
        //1. 获取ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 判断ssid对应的session对象是否存在
        session_t ssp = _sm.find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            resp["result"] = false;
            resp["reason"] = "游戏房间长连接创建失败!原因：session对象不存在！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        //3. 获得用户的id
        uint64_t uid = ssp->get_user_id();
        //4. 将用户加入到在线用户管理模块当中
        _om.enter_room(uid,conn);
        //5. 定时器设置为永久
        _sm.set_session_expire_time(ssid,SESSION_FOREVER);
        //6. 返回响应
        size_t room_id = _rm.find_by_uid(uid);
        room_t rp = _rm.find_by_room_id(room_id);
        resp["optype"] = "room_ready";
        resp["result"] = true;
        resp["uid"] = (Json::Int64)uid;
        resp["room_id"] = (Json::Int64)room_id;
        resp["white_id"] = (Json::Int64)rp->use_white_uid();
        resp["black_id"] = (Json::Int64)rp->use_black_uid();
        std::string body;
        json_util::serialize(resp,body);
        conn->send(body);
    }

    void hall_message(wsserver_t::connection_ptr conn,wsserver_t::message_ptr msg)// 游戏大厅的事务处理
    {
        Json::Value resp;
        //1. 获得ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 用户是否在线
        session_t ssp = _sm.find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            ERROR_LOG("处理游戏大厅事务失败！原因：未能获得ssid%d对应的session对象!",ssid);
            resp["result"] = false;
            resp["reason"] = "处理游戏大厅事务失败！原因：未能获得session对象!";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        //3. 获取具体事务信息
        std::string req_str = msg->get_payload();// 获取websocket请求的正文
        Json::Value req;
        json_util::deserialize(req_str,req);// 反序列化，方便处理
        //4. 要么匹配、要么取消匹配，其他情况则为错误请求
        uint64_t uid = ssp->get_user_id();
        if(req["optype"].asString().empty())
        {
            ERROR_LOG("处理游戏大厅事务失败！原因：客户端发送的操作类型为空！");
            resp["result"] = false;
            resp["reason"] = "处理游戏大厅事务失败！原因：客户端发送的操作类型为空！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        DEBUG_LOG("获得客户端游戏大厅事务信息：%s",req["optype"].asString().c_str());
        if(req["optype"].asString() == "match_start")
        {
            _mm.add(uid);// 加入到匹配队列里面去
            resp["optype"] = "match_start";
            resp["result"] = true;
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        else if(req["optype"].asString() == "match_stop")
        {
            _mm.del(uid);// 从匹配队列当中取消
            resp["optype"] = "match_stop";
            resp["result"] = true;
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        resp["optype"] = "uknow";
        resp["result"] = false;
        resp["reason"] = "未知操作！";
        std::string body;
        json_util::serialize(resp,body);
        conn->send(body);
    }

    void room_message(wsserver_t::connection_ptr conn,wsserver_t::message_ptr msg)// 游戏房间的事务处理
    {
        Json::Value resp;
        //1. 获得ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 判断ssid是否存在
        session_t ssp = _sm.find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            resp["result"] = false;
            resp["reason"] = "游戏房间的事务处理失败！原因：session对象不存在！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        //3. 获取具体的事务信息
        std::string req_str = msg->get_payload();// 获取正文
        Json::Value req_json;
        json_util::deserialize(req_str,req_json);
        //4. 获得用户uid
        uint64_t uid = ssp->get_user_id();
        //5. 判断用户是否在线
        bool ret = _om.is_in_room(uid);
        if(ret == false)
        {
            resp["result"] = false;
            resp["reason"] = "游戏房间的事务处理失败！原因：用户不在游戏房间当中！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        //6. 根据事务信息做出不同的动作
        if(req_json["optype"].asString().empty())
        {
            resp["result"] = false;
            resp["reason"] = "游戏房间的事务处理失败！原因：非法事务请求！";
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
            return;
        }
        if(req_json["optype"].asString() == "put_chess")
        {
            size_t room_id = req_json["room_id"].asInt64();// 如果是下棋动作就要先获取用户所在的房间号
            room_t rp = _rm.find_by_room_id(room_id);
            bool ret = rp->handle_request(req_json);
            if(ret == false)
            {
                resp["result"] = false;
                resp["reason"] = "游戏房间的事务处理失败！原因：服务器错误！";
                std::string body;
                json_util::serialize(resp,body);
                conn->send(body);
                return;
            }
            return;
        }
        else if(req_json["optype"].asString() == "chat")
        {
            size_t room_id = req_json["room_id"].asInt64();
            room_t rp = _rm.find_by_room_id(room_id);
            bool ret = rp->handle_request(req_json);
            if(ret == false)
            {
                resp["result"] = false;
                resp["reason"] = "游戏房间的事务处理失败！原因：服务器错误！";
                std::string body;
                json_util::serialize(resp,body);
                conn->send(body);
                return;
            }
            return;
        }
    }

    //14. 游戏大厅断开连接
    void websocket_hall_broken(wsserver_t::connection_ptr conn)
    {
        //1. 获取ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 判断ssid对应的session对象是否存在
        session_t ssp = _sm.find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            return;
        }
        //3. 获取用户id
        uint64_t uid = ssp->get_user_id();
        //4. 判断该用户是否在线
        bool ret = _om.is_in_hall(uid);
        if(ret == false)
        {
            return;
        }
        //5. 从匹配队列当中删除该用户
        _mm.del(uid);
        //6. 将该用户的session定时器重置
        _sm.set_session_expire_time(ssid,SESSION_TIMEOUT);// 这个不设置会内存泄漏
        //7. 将该用户从在线用户管理模块中移除
        _om.exit_hall(uid);
    }

    
    void websocket_room_broken(wsserver_t::connection_ptr conn)// 游戏房间断开连接
    {
        //1. 获取ssid
        size_t ssid = get_ssid_from_cookie(conn);
        //2. 判断ssid对应的session对象是否存在
        session_t ssp = _sm.find_by_ssid(ssid);
        if(ssp.get() == nullptr)
        {
            ERROR_LOG("游戏房间连接断开失败！原因：session对象不存在！");
            return;
        }
        //3. 获取用户id
        uint64_t uid = ssp->get_user_id();
        //4. 判断用户是否在线(处于在线状态的用户才能退出)
        bool ret = _om.is_in_room(uid);
        if(ret == false)
        {
            ERROR_LOG("游戏房间连接断开失败！原因：用户不在游戏房间中！");
            return;
        }
        //5. 从游戏房间当中退出
        size_t room_id = _rm.find_by_uid(uid);
        room_t rp = _rm.find_by_room_id(room_id);
        // //  5.1 退出游戏房间之前先判断是否有两名或两名以上的玩家
        // size_t count = rp->use_user_count();
        //rp->exit_room(uid);
        _rm.exit_room(uid);
        //6. 重置session对象
        _sm.set_session_expire_time(ssid,SESSION_TIMEOUT);
        //7. 从在线管理模块当中移除用户
        _om.exit_room(uid);
    }
protected:
    gobang_server()// 构造函数
        : _rm(&_dm, &_om), _sm(&_om,&_server), _mm(&_dm, &_om, &_rm)
    {
        // 1. 创建一个server对象
        //  2. 设置日志等级
        _server.set_access_channels(websocketpp::log::alevel::none);
        // 3. 初始化asio调度器
        _server.init_asio();
        _server.set_reuse_addr(true);
        // 4. 设置回调函数
        _server.set_open_handler(std::bind(&gobang_server::open_callback, this, std::placeholders::_1));
        _server.set_close_handler(std::bind(&gobang_server::close_callback, this, std::placeholders::_1));
        _server.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _server.set_message_handler(std::bind(&gobang_server::message_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

    data_manager _dm;
    online_manager _om;
    room_manager _rm;
    wsserver_t _server;
    session_manager _sm;
    match_manager _mm;
    std::string wwwroot = "./wwwroot";
    static gobang_server *_pgs;
    static std::mutex _mutex;
};
gobang_server *gobang_server::_pgs = nullptr;
std::mutex gobang_server::_mutex;